/* * Sun Public License Notice * * The contents of this file are subject to the Sun Public License * Version 1.0 (the "License"). You may not use this file except in * compliance with the License. A copy of the License is available at * http://www.sun.com/ * * The Original Code is Forte for Java, Community Edition. The Initial * Developer of the Original Code is Sun Microsystems, Inc. Portions * Copyright 1997-2000 Sun Microsystems, Inc. All Rights Reserved. */ package org.netbeans.editor.ext; import java.awt.Dimension; import java.awt.Rectangle; import java.awt.Color; import java.awt.Insets; import java.awt.BorderLayout; import java.awt.Component; import java.awt.event.KeyEvent; import java.awt.event.KeyAdapter; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import java.awt.event.FocusAdapter; import java.awt.event.FocusEvent; import java.beans.PropertyChangeListener; import java.beans.PropertyChangeEvent; import java.util.List; import java.util.Iterator; import java.util.Map; import java.util.HashMap; import javax.swing.Action; import javax.swing.BorderFactory; import javax.swing.JComponent; import javax.swing.JList; import javax.swing.JLabel; import javax.swing.JPanel; import javax.swing.JRootPane; import javax.swing.JLayeredPane; import javax.swing.JScrollPane; import javax.swing.JViewport; import javax.swing.KeyStroke; import javax.swing.ListCellRenderer; import javax.swing.AbstractListModel; import javax.swing.SwingUtilities; import javax.swing.Timer; import javax.swing.SwingUtilities; import javax.swing.event.DocumentListener; import javax.swing.event.DocumentEvent; import javax.swing.text.JTextComponent; import javax.swing.text.BadLocationException; import javax.swing.text.EditorKit; import javax.swing.text.Document; import org.netbeans.editor.*; /** * Java completion display formatting and services * * @author Miloslav Metelka * @version 1.00 */ public class JCView extends JScrollPane implements ActionListener, SettingsChangeListener { private static final Dimension MAX_SIZE = new Dimension(400, 300); private static final Dimension MIN_SIZE = new Dimension(100, 50); private static final Dimension PLUS_SIZE = new Dimension(20, 20); public static final String JCOMPLETION_VIEW_PROP = "jcompletion-view"; // NOI18N static final long serialVersionUID =2254850878595695671L; private JList list; protected JTextComponent component; private JCQuery query; private JCQuery.QueryResult queryResult; JLabel topLabel; Timer refreshTimer; private DocumentListener docL; private PropertyChangeListener docChangeL; /** Reserved space around the caret */ static final int CARET_THRESHOLD = 5; private static JCView getFromExtUI(JTextComponent c) { return (JCView)Utilities.getExtUI(c).getProperty(JCOMPLETION_VIEW_PROP); } public static JCView getView(JTextComponent c) { JCView view = getFromExtUI(c); if (view == null) { view = new JCView(c); Utilities.getExtUI(c).putProperty(JCOMPLETION_VIEW_PROP, view); } return view; } public static boolean isViewVisible(JTextComponent c) { JCView view = getFromExtUI(c); boolean v = false; if (view != null) { v = view.isVisible(); } return v; } public static void setViewVisible(JTextComponent c, boolean visible) { if (visible) { JCView view = getView(c); if (view != null) { view.showHelp(false); } } else { if (isViewVisible(c)) { JCView view = getView(c); view.setVisible(false); } } } public static void refreshView(JTextComponent c, boolean post) { if (c != null) { JCView view = getFromExtUI(c); if (view != null && view.isVisible()) { // visible view.showHelp(post); } } } public static Object getSelectedValue(JTextComponent c) { if (c != null) { JCView view = getFromExtUI(c); if (view != null && view.isVisible()) { return view.getSelectedValue(); } } return null; } public static Object getFirstResultItem(JTextComponent c, int pos) { JCView view = getView(c); if (view != null) { JCQuery query = view.getQuery(); if (query != null) { JCQuery.QueryResult qr = query.getHelp(c, pos, true); if (qr != null) { List data = qr.getData(); if (data != null && data.size() > 0) { return data.get(0); } } } } return null; } /** Construct new view */ public JCView(JTextComponent component) { super(); // Prevent the bug with displaying without the scrollbar getViewport().setMinimumSize(new Dimension(4,4)); this.component = component; super.setVisible(false); installInnerComponents(); init(); putClientProperty ("HelpID", JCView.class.getName ()); // NOI18N } private void init() { BaseKit kit = Utilities.getKit(component); if (kit != null) { getList().registerKeyboardAction(kit.getActionByName(JavaKit.jCompletionHideAction), KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0), JComponent.WHEN_FOCUSED ); } component.addFocusListener( new FocusAdapter() { public void focusLost(FocusEvent evt) { SwingUtilities.invokeLater( new Runnable() { public void run() { if (isVisible()) { Component focusOwner = SwingUtilities.windowForComponent( component).getFocusOwner(); if (!(focusOwner != null && (focusOwner == component || focusOwner == JCView.this || focusOwner == getList()) )) { setVisible(false); // both JC and component don't own the focus } } } } ); } } ); // Create timer; delay will be changed later refreshTimer = new Timer(500, this); refreshTimer.setRepeats(false); Settings.addSettingsChangeListener(this); settingsChange(null); // Create document listener docL = new DocumentListener() { public void insertUpdate(DocumentEvent e) { if (e.getLength() > 0) { invalidateQueryResult(); } } public void removeUpdate(DocumentEvent e) { if (e.getLength() > 0) { invalidateQueryResult(); } } public void changedUpdate(DocumentEvent e) { } }; // Document change listener docChangeL = new PropertyChangeListener() { public void propertyChange(PropertyChangeEvent evt) { if ("document".equals(evt.getPropertyName())) { Document oldDoc = (Document)evt.getOldValue(); Document newDoc = (Document)evt.getNewValue(); oldDoc.removeDocumentListener(docL); newDoc.addDocumentListener(docL); } } }; Document doc = component.getDocument(); doc.addDocumentListener(docL); component.addPropertyChangeListener(docChangeL); } public void settingsChange(SettingsChangeEvent evt) { Class kitClass = Utilities.getKitClass(component); int delay = SettingsUtil.getInteger(kitClass, ExtSettings.JCOMPLETION_REFRESH_DELAY, ExtSettings.defaultJCRefreshDelay); refreshTimer.setDelay(delay); } private void checkAddToPane() { // Install into layered pane JRootPane rp = component.getRootPane(); if (rp == null) { // null RootPane! System.err.println("Editor component has no RootPane! Cannot install Java Completion JList"); // NOI18N } JRootPane thisRP = getRootPane(); if (thisRP != rp) { if (thisRP != null) { thisRP.remove(this); } rp.getLayeredPane().add(this, JLayeredPane.POPUP_LAYER, 0); } } public final JCQuery getQuery() { if (query == null) { query = createQuery(); } return query; } protected JCQuery createQuery() { return new JCQuery(); } protected void installInnerComponents() { list = createList(); setViewportView(list); // getViewport().setMinimumSize(new Dimension(4,4)); topLabel = createTopLabel(); setColumnHeaderView(topLabel); } public JLabel getTopLabel() { return topLabel; } protected ListCellRenderer createCellRenderer() { return (ListCellRenderer)SettingsUtil.getValue(Utilities.getKitClass(component), ExtSettings.JCOMPLETION_CELL_RENDERER, new JCCellRenderer()); } protected JList createList() { JList l = new JList(); l.setCellRenderer(createCellRenderer()); l.addMouseListener(new JCMouseListener()); return l; } protected JLabel createTopLabel() { JLabel l = new JLabel(); l.setForeground(Color.blue); l.setBorder(BorderFactory.createEmptyBorder(0, 2, 0, 2)); return l; } public JList getList() { return list; } /** Populate the completion list with the results of the query. */ synchronized void populate(JCQuery.QueryResult queryResult) { this.queryResult = queryResult; // will be used when updating the text if (queryResult != null) { // Set the top title getTopLabel().setText(queryResult.getTitle()); // Set the class display offset ListCellRenderer r = getList().getCellRenderer(); if (r instanceof JCCellRenderer) { ((JCCellRenderer)r).setClassDisplayOffset(queryResult.getClassDisplayOffset()); } // Set the result data List data = queryResult.getData(); if (data != null) { getList().setModel(new JCListModel(data)); if (data.size() > 0) { getList().setSelectedIndex(0); } } setVisible(true); } } public synchronized boolean updateCommonText() { return getQuery().updateCommonText(component, queryResult); } public synchronized boolean updateText() { Object replacement = getList().getSelectedValue(); List data = ((JCListModel)getList().getModel()).getData(); return getQuery().updateText(component, data, replacement, queryResult); } private JList getNonEmptyList() { JList l = getList(); if (l.getModel().getSize() <= 0) { l = null; } return l; } public void moveDown() { JList l = getNonEmptyList(); if (l != null) { l.setSelectedIndex(Math.min(l.getSelectedIndex() + 1, l.getModel().getSize() - 1)); l.ensureIndexIsVisible(l.getSelectedIndex()); } } public void moveUp() { JList l = getNonEmptyList(); if (l != null) { l.setSelectedIndex(l.getSelectedIndex() - 1); l.ensureIndexIsVisible(l.getSelectedIndex()); } } public void movePageDown() { JList l = getNonEmptyList(); if (l != null) { int pageSize = Math.max(l.getLastVisibleIndex() - l.getFirstVisibleIndex(), 0); int lastInd = Math.max(Math.min(l.getLastVisibleIndex() + pageSize, l.getModel().getSize() - 1), 0); int ind = Math.max(Math.min(l.getSelectedIndex() + pageSize, lastInd), 0); l.ensureIndexIsVisible(lastInd); l.setSelectedIndex(ind); l.ensureIndexIsVisible(ind); } } public void movePageUp() { JList l = getNonEmptyList(); if (l != null) { int pageSize = Math.max(l.getLastVisibleIndex() - l.getFirstVisibleIndex(), 0); int firstInd = Math.max(l.getFirstVisibleIndex() - pageSize, 0); int ind = Math.max(l.getSelectedIndex() - pageSize, firstInd); l.ensureIndexIsVisible(firstInd); l.setSelectedIndex(ind); l.ensureIndexIsVisible(ind); } } public void moveBegin() { JList l = getNonEmptyList(); if (l != null) { l.setSelectedIndex(0); l.ensureIndexIsVisible(0); } } public void moveEnd() { JList l = getNonEmptyList(); if (l != null) { int ind = l.getModel().getSize() - 1; l.setSelectedIndex(ind); l.ensureIndexIsVisible(ind); } } public Object getSelectedValue() { return getList().getSelectedValue(); } public synchronized void setVisible(final boolean visible) { if (visible) { checkAddToPane(); } else { refreshTimer.stop(); } super.setVisible(visible); if (visible) { getList().setSelectedIndex(0); getList().ensureIndexIsVisible(getList().getSelectedIndex()); SwingUtilities.invokeLater( new Runnable() { public void run() { Rectangle bounds = getPreferredBounds(); setBounds(bounds); revalidate(); } } ); } else { // making invisible component.requestFocus(); } } protected Rectangle getPreferredBounds() { Rectangle r = new Rectangle(getList().getPreferredSize()); r.width += PLUS_SIZE.width; r.height += PLUS_SIZE.height; // update by top label width Dimension tld = topLabel.getPreferredSize(); Insets i = getInsets(); if (i != null) { tld.width += i.left + i.right; } r.width = Math.max(r.width, tld.width); r.width = Math.min(Math.max(r.width, MIN_SIZE.width), MAX_SIZE.width); r.height = Math.min(Math.max(r.height, MIN_SIZE.height), MAX_SIZE.height); ExtUI extUI = Utilities.getExtUI(component); Rectangle extBounds = extUI.getExtentBounds(); Rectangle caretRect = (Rectangle)component.getCaret(); // Compute y coord. under the caret relative to the beggining of window int underCaretStartRelY = caretRect.y + caretRect.height + CARET_THRESHOLD - extBounds.y; // Compute y coord. above the caret int aboveCaretEndRelY = (caretRect.y - extBounds.y) - CARET_THRESHOLD; // Update y and height if (extBounds.height - underCaretStartRelY >= r.height) { // enough space under caret? r.y = underCaretStartRelY; } else if (extBounds.height - underCaretStartRelY >= aboveCaretEndRelY) { r.y = underCaretStartRelY; // more space under caret than up from it r.height = extBounds.height - underCaretStartRelY; } else { // more space up from top of window till the caret position r.height = Math.min(aboveCaretEndRelY, r.height); r.y = aboveCaretEndRelY - r.height; } // Update x and width if (r.width < extBounds.width) { r.x = Math.min(caretRect.x - extBounds.x, extBounds.width - r.width); } else { // width too big r.x = 0; } return r; } /** Show the code completion help. * @param post post the request so the timer is started and * the code completion window is refreshed when the timer expires. */ public synchronized void showHelp(boolean post) { if (isVisible() && post) { // view already visible refreshTimer.start(); } else { // not yet visible, display immediately refreshTimer.stop(); actionPerformed(null); } } public synchronized void invalidateQueryResult() { queryResult = null; } /** It's called to notify that the refresh timer was triggered. */ public void actionPerformed(ActionEvent evt) { populate(getQuery().getHelp(component)); } public static class JCListModel extends AbstractListModel { List data; static final long serialVersionUID =3292276783870598274L; public JCListModel(List data) { this.data = data; } public int getSize() { return data.size(); } public Object getElementAt(int index) { return (index >= 0 && index < data.size()) ? data.get(index) : null; } List getData() { return data; } } static class JCMouseListener extends MouseAdapter { public void mouseClicked(MouseEvent evt) { if (SwingUtilities.isLeftMouseButton(evt) && evt.getClickCount() == 2 ) { Object src = evt.getSource(); JTextComponent target = null; if (src instanceof Component) { JCView v = (JCView)SwingUtilities.getAncestorOfClass(JCView.class, (Component)src); target = v.component; } if (target != null) { BaseKit kit = Utilities.getKit(target); if (kit != null) { Action a = kit.getActionByName(JavaKit.insertBreakAction); if (a != null) { a.actionPerformed(new ActionEvent(target, ActionEvent.ACTION_PERFORMED, "")); // NOI18N } } } } } } } /* * Log * 28 Gandalf-post-FCS1.26.1.0 3/8/00 Miloslav Metelka * 27 Gandalf 1.26 1/16/00 Miloslav Metelka * 26 Gandalf 1.25 1/15/00 Miloslav Metelka #5270 * 25 Gandalf 1.24 1/13/00 Miloslav Metelka Localization * 24 Gandalf 1.23 1/11/00 Jesse Glick Context help. * 23 Gandalf 1.22 1/10/00 Miloslav Metelka * 22 Gandalf 1.21 11/14/99 Miloslav Metelka * 21 Gandalf 1.20 11/9/99 Miloslav Metelka * 20 Gandalf 1.19 11/8/99 Miloslav Metelka * 19 Gandalf 1.18 10/23/99 Ian Formanek NO SEMANTIC CHANGE - Sun * Microsystems Copyright in File Comment * 18 Gandalf 1.17 10/11/99 Miloslav Metelka fixed focus problems * 17 Gandalf 1.16 10/10/99 Miloslav Metelka * 16 Gandalf 1.15 10/4/99 Miloslav Metelka * 15 Gandalf 1.14 8/18/99 Miloslav Metelka * 14 Gandalf 1.13 8/18/99 Miloslav Metelka * 13 Gandalf 1.12 8/17/99 Miloslav Metelka * 12 Gandalf 1.11 8/10/99 Miloslav Metelka no gray edges * 11 Gandalf 1.10 8/9/99 Ian Formanek Generated Serial Version * UID * 10 Gandalf 1.9 7/30/99 Miloslav Metelka * 9 Gandalf 1.8 7/29/99 Miloslav Metelka * 8 Gandalf 1.7 7/26/99 Miloslav Metelka * 7 Gandalf 1.6 7/21/99 Miloslav Metelka * 6 Gandalf 1.5 7/21/99 Miloslav Metelka * 5 Gandalf 1.4 7/20/99 Miloslav Metelka * 4 Gandalf 1.3 6/25/99 Miloslav Metelka from floats back to ints * 3 Gandalf 1.2 6/10/99 Miloslav Metelka * 2 Gandalf 1.1 6/10/99 Miloslav Metelka * 1 Gandalf 1.0 6/8/99 Miloslav Metelka * $ */